<?php
namespace app\extra;
use think\facade\Config;
use think\facade\Request;
use think\Controller;

abstract class Rest extends Controller{
    protected $request;
    protected $_method = ''; // 当前请求类型
    protected $_type   = ''; // 当前资源类型
    protected $_version = ''; //请求接口版本号
    protected $_noAuthorization = [];  //无需权限检测的api
    public static $_login_key = 'auth_key';  //  session 保存的键名
    // 输出类型
    protected $restMethodList    = 'get|post|put|delete';
    protected $restDefaultMethod = 'get';
    protected $restTypeList      = 'html|xml|json|rss';
    protected $restDefaultType   = 'json';
    protected $restOutputType    = [ // REST允许输出的资源类型列表
        'xml'  => 'application/xml',
        'json' => 'application/json',
        'html' => 'text/html',
    ];
    protected $restInvokeList = [
        'get' => 'gets',
        'post' => 'create',
        'put' => 'update',
        'delete' => 'delete'
    ];

    protected $_filter;

    protected $_node_permission;

    /**
     * 架构函数 取得模板对象实例
     * @access public
     */
    public function __construct()
    {
        // 初始化 父类
        parent::__construct();
        // 资源类型检测
        $this->_type = $this->getAcceptType();
        // 请求方式检测
        $method = strtolower($_SERVER['REQUEST_METHOD']);
        //跨域嗅探,直接返回200
        if($method == 'options'){
            $this->response('', true);
        }
        //参数过滤
        $this->filterHandle();
        if (false === stripos($this->restMethodList, $method)) {
            // 请求方式非法 则用默认请求方法
            $method = $this->restDefaultMethod;
        }
        $this->_method = $method;
        $this->getVersion();
        $this->route();
        //匹配
    }
    /**
     * REST 调用
     * @throws \Exception
     */
    protected function route()
    {
        $func = $this->restInvokeList[$this->_method];
        $method = $func;

        if($this->_version != ''){
            $func .= '_v' . $this->_version;
        }

        if(method_exists($this, $method)){
            if(!$this->_noAuthorization || !in_array($method, $this->_noAuthorization)){
                $this->auth();
            }
            //$this->log();
            $data = $this->$func();
            if (is_array($data)){
                $data = $this->encodeData($data, $this->_type);
                echo $data;
                exit();
            }  else {
                exit($data);
            }

        }
        else{
            throw new \Exception('error action :' . $method);
        }
    }

     /**
     * 获取当前请求的Accept头信息
     * @return string
     */
    protected function getAcceptType(){
        $type = array(
            'xml'   =>  'application/xml,text/xml,application/x-xml',
            'json'  =>  'application/json,text/x-json,application/jsonrequest,text/json',
            'js'    =>  'text/javascript,application/javascript,application/x-javascript',
            'css'   =>  'text/css',
            'rss'   =>  'application/rss+xml',
            'yaml'  =>  'application/x-yaml,text/yaml',
            'atom'  =>  'application/atom+xml',
            'pdf'   =>  'application/pdf',
            'text'  =>  'text/plain',
            'png'   =>  'image/png',
            'jpg'   =>  'image/jpg,image/jpeg,image/pjpeg',
            'gif'   =>  'image/gif',
            'csv'   =>  'text/csv',
            'html'  =>  'text/html,application/xhtml+xml'
        );

        foreach($type as $key=>$val){
            $array   =  explode(',',$val);
            foreach($array as $k=>$v){
                if(stristr($_SERVER['HTTP_ACCEPT'], $v)) {
                    return $key;
                }
            }
        }
        return $this->restDefaultType;
    }

    protected function getVersion(){
        $word_reg = "/.*version=(\d+(?:\.\d+)?)/";
        if(preg_match($word_reg, $_SERVER['HTTP_ACCEPT'], $matches)){
            $this->_version = str_replace('.', '_', $matches[1]);
        }
    }

    /**
     * 发送Http状态信息
     * @param $code
     */
    protected function sendHttpStatus($code) {
        static $_status = array(
            // Informational 1xx
            100 => 'Continue',
            101 => 'Switching Protocols',
            // Success 2xx
            200 => 'OK',
            201 => 'Created',
            202 => 'Accepted',
            203 => 'Non-Authoritative Information',
            204 => 'No Content',
            205 => 'Reset Content',
            206 => 'Partial Content',
            // Redirection 3xx
            300 => 'Multiple Choices',
            301 => 'Moved Permanently',
            302 => 'Moved Temporarily ',  // 1.1
            303 => 'See Other',
            304 => 'Not Modified',
            305 => 'Use Proxy',
            // 306 is deprecated but reserved
            307 => 'Temporary Redirect',
            // Client Error 4xx
            400 => 'Bad Request',
            401 => 'Unauthorized',
            402 => 'Payment Required',
            403 => 'Forbidden',
            404 => 'Not Found',
            405 => 'Method Not Allowed',
            406 => 'Not Acceptable',
            407 => 'Proxy Authentication Required',
            408 => 'Request Timeout',
            409 => 'Conflict',
            410 => 'Gone',
            411 => 'Length Required',
            412 => 'Precondition Failed',
            413 => 'Request Entity Too Large',
            414 => 'Request-URI Too Long',
            415 => 'Unsupported Media Type',
            416 => 'Requested Range Not Satisfiable',
            417 => 'Expectation Failed',
            444 => 'Unauthorized',
            // Server Error 5xx
            500 => 'Internal Server Error',
            501 => 'Not Implemented',
            502 => 'Bad Gateway',
            503 => 'Service Unavailable',
            504 => 'Gateway Timeout',
            505 => 'HTTP Version Not Supported',
            509 => 'Bandwidth Limit Exceeded'
        );
        if(isset($_status[$code])) {
            header('HTTP/1.1 '.$code.' '.$_status[$code]);
            // 确保FastCGI模式下正常
            header('Status:'.$code.' '.$_status[$code]);
        }
    }

    /**
     * 编码数据
     * @access protected
     * @param mixed $data 要返回的数据
     * @param String $type 返回类型 JSON XML
     * @return string
     */
    protected function encodeData($data,$type='') {
        if(empty($data))  return '';
        if('json' == $type) {
            // 返回JSON数据格式到客户端 包含状态信息
            $data = json_encode($data);
        }elseif('xml' == $type){
            // 返回xml格式数据
            $data = $this->xml_encode($data);
        }elseif('php'==$type){
            $data = serialize($data);
        } else {
            if (is_array($data)){
                $data = json_encode($data);
            }
        }
        $this->setContentType($type);
        //header('Content-Length: ' . strlen($data));
        return $data;
    }

    /**
     * 设置页面输出的CONTENT_TYPE和编码
     * @access public
     * @param string $type content_type 类型对应的扩展名
     * @param string $charset 页面输出编码
     * @return void
     */
    public function setContentType($type, $charset='utf-8'){
        if(headers_sent()) return;
        if(empty($charset))  $charset = config('DEFAULT_CHARSET');
        $type = strtolower($type);
        if(isset($this->restOutputType[$type])) //过滤content_type
            header('Content-Type: '.$this->restOutputType[$type].'; charset='.$charset);
        if(config('CORS')){
            header("Access-Control-Allow-Origin:". \config('CORS'));
            header("Access-Control-Allow-Headers:Authorization,Accept,Device");
            header("Access-Control-Allow-Methods:GET,POST,PUT,DELETE,OPTIONS");
        }
    }

    /**
     * 输出返回数据
     * @access protected
     * @param mixed $data 要返回的数据
     * @param String $type 返回类型 JSON XML
     * @param integer $code HTTP状态
     * @return void
     */
    protected function response($message, $status, $data = '', $code = 200) {
        $this->sendHttpStatus($code);
        $return_data['status'] = $status;
        $return_data['message'] = $message;
        $return_data['data'] = $data;
        if(ApiToken::$send_flg && ApiToken::get(self::$_login_key)){
            setcookie(\app\extra\behavior\AuthBehavior::$authKey, ApiToken::$sid, $expire = 0, '/');
            $return_data['token_id'] = ApiToken::$sid;
        }
        exit($this->encodeData($return_data,strtolower($this->_type)));
    }

    protected function auth(){
        if(!ApiToken::get(self::$_login_key)){
            $this->response('未登录', false, '', 401);
        }
    }

    protected function filterHandle(){
        if(!$this->_filter){
            return;
        }

        foreach($this->_filter as $v){
            $input_value = I($v[0]);
            if(!$input_value)
                continue;

            switch($v[1]){
                case 'isExists':
                    if(D($v[2])->isExists($input_value) === false){
                        $this->response('数据不存在', 0, '', $v[3]);
                    }
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * 成功
     * @param array $data
     * @param string $message
     * @param int $count
     * @param int $page_number
     * @param int $page
     */
    public function responseSuccess($data=[], $message='success', $count=0, $page_number=0, $page=0){
        if ($count !== 0 || $page_number !== 0 ){
            //总数
            $jsonData['count'] = $count;
            //分页数
            $jsonData['size'] = $page_number;
            if ($page == 0){
                $jsonData['page'] = input('page', 1);
            } else {
                $jsonData['page'] = $page;
            }

            //当前记录列表
            if(empty($data)){
                $jsonData['list'] = [];
            }else{
                $jsonData['list'] = $data;
            }
        } else {
            $jsonData = $data;
        }
        $this->response($message, true, $jsonData);
    }
    /**
     * 失败
     * @param string $message       错误信息
     * @param array $data           可带数据
     */
    public function responseFail($message='Error message',$data = []){
        $this->response($message,false,$data);
    }
    /**
     * 错误
     * @param string $message
     * @param array $data
     */
    public function responseError($message='访问出错了，请重试',$data = []){
        $this->response($message,false,$data, 401);
    }

    /**
     * 设置登陆的token
     * @param $value
     * @param null $expire
     */
    public function setUser($value, $expire = null){
        ApiToken::set(self::$_login_key, $value, $expire);
    }
    /**
     * 获得token 缓存的值
     * @return mixed|null
     */
    public function getUser(){
        return ApiToken::get(self::$_login_key);
    }
    /**
     * 获取缓存的属性值
     * @param string $key
     * @return mixed
     */
    public function getUserAttr($key = 'id'){
       $user = ApiToken::get(self::$_login_key);
       return $user[$key];
    }
    public function xml_encode($data, $root='think', $item='item', $attr='', $id='id', $encoding='utf-8') {
        if(is_array($attr)){
            $_attr = array();
            foreach ($attr as $key => $value) {
                $_attr[] = "{$key}=\"{$value}\"";
            }
            $attr = implode(' ', $_attr);
        }
        $attr   = trim($attr);
        $attr   = empty($attr) ? '' : " {$attr}";
        $xml    = "<?xml version=\"1.0\" encoding=\"{$encoding}\"?>";
        $xml   .= "<{$root}{$attr}>";
        $xml   .= $this->data_to_xml($data, $item, $id);
        $xml   .= "</{$root}>";
        return $xml;
    }
    /**
     * 数据XML编码
     * @param mixed  $data 数据
     * @param string $item 数字索引时的节点名称
     * @param string $id   数字索引key转换为的属性名
     * @return string
     */
    protected function data_to_xml($data, $item='item', $id='id') {
        $xml = $attr = '';
        foreach ($data as $key => $val) {
            if(is_numeric($key)){
                $id && $attr = " {$id}=\"{$key}\"";
                $key  = $item;
            }
            $xml    .=  "<{$key}{$attr}>";
            $xml    .=  (is_array($val) || is_object($val)) ? $this->data_to_xml($val, $item, $id) : $val;
            $xml    .=  "</{$key}>";
        }
        return $xml;
    }
    // 请求数据的方法合集
    /**
     * 方法使用的 用来以分页形式返回数据列表
     * @param array $map
     * @param string $order
     * @param mixed|string $model
     */
    public function responsePage($map=[], $order = '',$model=''){
        if (empty($model)){
            $model = $this->controller;
        }
        $model = model($model);
        $count = $model->getListForCount($map);
        $per_page = input('get.size', 20);
        $page = input('get.page', 1);
        $data_list = $model->getListForPage($map, $page, $per_page, $order);
        $this->responseSuccess($data_list, '查询成功', $count, $per_page);
    }

    /**
     * 获取一条数据
     * @param $id
     * @param string $model
     */
    public function getOne($id,$model=''){
        if (empty($model)){
            $model = $this->controller;
        }
        $model = model($model);
        $data = $model->getOne($id);
        $this->responseSuccess($data);
    }
}
